

import javax.tools.Tool;

import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.stat.correlation.Covariance;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import weka.core.Instance;

	
public class DataWindow {
	// 50ms*256 = 12.8sec, let's reduce the window size to 128
	private static final int WINDOW_SIZE = 128;
	private DataPoint[] samples;
	
	private int index;
	
	private Covariance cov = new Covariance();
	
	private double xMean;
	private double yMean;
	private double zMean;
	private double xStdDev;
	private double yStdDev;
	private double zStdDev;
	private double xEnergy;
	private double yEnergy;
	private double zEnergy;
	private double xyCorrelation;
	private double yzCorrelation;
	private double zxCorrelation;
	
	private ClassifierBuilder cb;
	
	public DataWindow(ClassifierBuilder classifier) {
		index = 0;
		samples = new DataPoint[WINDOW_SIZE];
		cb = classifier;
	}
	
	/**
	 * Checks whether there is still space in this datawindow
	 * @return the result
	 */
	public boolean hasSpace() {
		return index<WINDOW_SIZE;
	}
	
	/**
	 * Adds a datapoint to this datawindow.
	 * @param the datapoint to add
	 * @return 0 if not possible, 1 on success
	 */
	public int addDataPoint(DataPoint d) {
		if (index>=WINDOW_SIZE)
			return 0;
		samples[index++] = d;
		if (index>=WINDOW_SIZE)
			postProcessing(); // ONCE DATAWINDOW IS FULL COMPUTE FEATURES
		return 1;
		
	}
	
	private double[] getXSamples() {
		double[] x = new double[WINDOW_SIZE];
		int i = 0;
		for (DataPoint d : samples) {
			x[i++] = d.getX();
		}
		return x;
	}
	
	private double[] getYSamples() {
		double[] y = new double [WINDOW_SIZE];
		int i = 0;
		for (DataPoint d : samples) {
			y[i++] = d.getY();
		}
		return y;				
	}
	
	private double[] getZSamples() {
		double[] z = new double [WINDOW_SIZE];
		int i = 0;
		for (DataPoint d : samples) {
			z[i++] = d.getZ();
		}
		return z;
	}
	
	/**
	 * processes all the attributes. This method is called when the DataWindow has been filled
	 */
	private void postProcessing() {
		DescriptiveStatistics xFactory = new DescriptiveStatistics();
		DescriptiveStatistics yFactory = new DescriptiveStatistics();
		DescriptiveStatistics zFactory = new DescriptiveStatistics();
		
		for (DataPoint d : samples) {
			xFactory.addValue(d.getX());
			yFactory.addValue(d.getY());
			zFactory.addValue(d.getZ());
		}
		
		xMean = xFactory.getMean();
		yMean = yFactory.getMean();
		zMean = zFactory.getMean();
		
		xStdDev = xFactory.getStandardDeviation();
		yStdDev = yFactory.getStandardDeviation();
		zStdDev = zFactory.getStandardDeviation();
		
		double[] xSamples = getXSamples();
		double[] ySamples = getYSamples();
		double[] zSamples = getZSamples();
		xEnergy = getEnergy(xSamples);
		yEnergy = getEnergy(ySamples);
		zEnergy = getEnergy(zSamples);
		
		xyCorrelation = cov.covariance(xSamples, ySamples);
		xyCorrelation /= (getXStdDeviation()*getYStdDeviation());
		yzCorrelation = cov.covariance(ySamples, zSamples);
		yzCorrelation /= (getYStdDeviation()*getZStdDeviation());
		zxCorrelation = cov.covariance(zSamples, xSamples);
		zxCorrelation /= (getZStdDeviation()*getXStdDeviation());
	}
	
	/**
	 * Tries to classify this DataWindow
	 * 
	 * @return the likelihood for each classes
	 * @throws Exception when the classifier is not built
	 */
	public double[] likelihood() throws Exception {
		// Get features from this datawindow
		Instance observations = this.featuresInstanceExtraction();
		
		// Use the classifier cb with this observation 
		double[] fDistrib;
		if (cb != null)
			fDistrib = cb.getLikelihood(observations);
		else 
			throw new Exception("No classifier specified");
		
		// Return result
		return fDistrib;
	}

	/**
	 * Try to count the number of steps using a peak detectin algorithm
	 * @return the number of steps counted
	 */
	public int countSteps() {
		double[] smoothCore = {1.0,2.0,2.0,2.0,1.0};
		double[][] values = smoothSamples(smoothCore);
		ToolBox.plot(values);
		return PeakDetectionAlgo.countPeaks(values[1]);
	}
	
	/**
	 * Plot three axis signal
	 */
	public void plotSignal() {
		double[][] axis = {getYSamples()};// Raw data
		ToolBox.plot(axis); 
	}
	
	/** 
	 * Basic function to see if movement is detected in this datawindow (average on the stdDeviation)
	 * @return true if there is momvent, false else
	 */
	public boolean isMouvement() {
		if ((getXStdDeviation()+getYStdDeviation()+getZStdDeviation())/3.0>ToolBox.MOVEMENT_THRESHOLD)
			return true;
		else
			return false;
	}

	public DataPoint[] getSamples() {
		return samples;
	}

	// =================================================================
	// MEAN AND STD DEVIATION FEATURES
	public double getXMean() { return xMean; }
	public double getYMean() { return yMean; }
	public double getZMean() { return zMean; }
	public double getXStdDeviation() { return xStdDev; }
	public double getYStdDeviation() { return yStdDev; }
	public double getZStdDeviation() { return zStdDev; }
	// =================================================================
	
	// =================================================================
	// ENERGY FEATURE
	public double getXEnergy() { return xEnergy;}
	public double getYEnergy() { return yEnergy;}
	public double getZEnergy() { return zEnergy;}
	
	/**
	 * Compute energy feature of an array
	 * @param input array
	 * @return energy value
	 */
	private double getEnergy(double[] array) {
		FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
		Complex[] result = fft.transform(array, TransformType.FORWARD);
		
		double sum = 0;
		
		for (int i=0; i<result.length; i++) {
			sum += Math.abs(result[i].getReal()*result[i].getReal() + result[i].getImaginary()*result[i].getImaginary());
		}
		
		return sum/(Math.pow((double)result.length, 2.0));
	}
	// =================================================================

	
	// =================================================================
	// CORRELATION FEATURE
	public double getXYCorrelation() { return xyCorrelation; }
	public double getYZCorrelation() { return yzCorrelation; }
	public double getZXCorrelation() { return zxCorrelation; }
	// =================================================================
	
	/**
	 * If the array is not full but no more data to put inside, let's fill the array by the same previous
	 * datapoint
	 */
	public void complete() {
		if (index>0) {
			DataPoint dp = samples[index-1];
			while (index<WINDOW_SIZE) {
				addDataPoint(new DataPoint(dp));
			}	
		}  else {
			// It's empty, can't complete!
			// Throw exception?
		}
	}
	
	public String featuresExtraction() {
		String data = getXMean()+",";
		data+= getYMean()+",";
		data+= getZMean()+",";
		data+= getXStdDeviation()+",";
		data+= getYStdDeviation()+",";
		data+= getZStdDeviation()+",";
		data+= getXEnergy()+",";
		data+= getYEnergy()+",";
		data+= getZEnergy()+",";
		data+= getXYCorrelation()+",";
		data+= getYZCorrelation()+",";
		data+= getZXCorrelation();
		
		return data;
	}
	
	public double[] featuresArrayExtraction() {
		double[] data = new double[13];
		data[0] = getXMean();
		data[1] = getYMean();
		data[2] = getZMean();
		data[3] = getXStdDeviation();
		data[4] = getYStdDeviation();
		data[5] = getZStdDeviation();
		data[6] = getXEnergy();
		data[7] = getYEnergy();
		data[8] = getZEnergy();
		data[9] = getXYCorrelation();
		data[10] = getYZCorrelation();
		data[11] = getZXCorrelation();
		
		return data;
	}
	
	public Instance featuresInstanceExtraction() {
		 // Create the instance
		 Instance iExample = new Instance(13);
		 iExample.setValue(0, getXMean());
		 iExample.setValue(1, getYMean());
		 iExample.setValue(2, getZMean());
		 iExample.setValue(3, getXStdDeviation());
		 iExample.setValue(4, getYStdDeviation());
		 iExample.setValue(5, getZStdDeviation());
		 iExample.setValue(6, getXEnergy());
		 iExample.setValue(7, getYEnergy());
		 iExample.setValue(8, getZEnergy());
		 iExample.setValue(9, getXYCorrelation());
		 iExample.setValue(10, getYZCorrelation());
		 iExample.setValue(11, getZXCorrelation());
		 iExample.setMissing(12);
		 
		 return iExample;
	}
	
	@Override
	public String toString() {
		String res = "";
		for (DataPoint sample : samples) {
			res += sample.toString() + "\n";
		}
		return res;
	}
	
	/**
	 * Method which smooths the three axis signals, apply a smoothing (moving average) to the signals in order to decrease noise twice.
	 * @param smoothCore
	 * @return the three axis smoothed signals
	 */
	final private double[][] smoothSamples(double[] smoothCore) {
		int n = samples.length;
		double avgX, avgY, avgZ, sum;
		
		int m = smoothCore.length;
		
		// Use a weighted sliding average on each signals before hand
		// TWICE!
		double[] x = new double[n];
		double[] y = new double[n];
		double[] z = new double[n];
		double valX=0.0, valY=0.0, valZ=0.0;
		for(int i=0;i<n;i++){
			avgX=0.0;
			avgY=0.0;
			avgZ=0.0;
			sum=0.0;
			for(int j=0;j<m;j++){
				int index = i-(m/2)+j;
				if (index>=0 && index < n) {
					avgX+=smoothCore[j]*samples[index].getX();
					avgY+=smoothCore[j]*samples[index].getY();
					avgZ+=smoothCore[j]*samples[index].getZ();
					sum += smoothCore[j];
				}
			}
			valX = avgX/sum; 
			valY = avgY/sum;
			valZ = avgZ/sum;
			
			x[i] = valX;
			y[i] = valY;
			z[i] = valZ;
		}
		for(int i=0;i<n;i++){
			avgX=0.0;
			avgY=0.0;
			avgZ=0.0;
			sum=0.0;
			for(int j=0;j<m;j++){
				int index = i-(m/2)+j;
				if (index>=0 && index < n) {
					avgX+=smoothCore[j]*x[index];
					avgY+=smoothCore[j]*y[index];
					avgZ+=smoothCore[j]*z[index];
					sum += smoothCore[j];
				}
			}
			valX = avgX/sum; 
			valY = avgY/sum;
			valZ = avgZ/sum;
			
			x[i] = valX;
			y[i] = valY;
			z[i] = valZ;
		
		}
		double[][] data = {x,y,z};

		return data;
	}
}

